﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AdapterBase.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the base class for all Adapters
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Adapters
{
    #region Namespace Imports

    using System;
    using System.Diagnostics;
    using System.IO;

    using Collaboris.Saf.Engine;
    using Collaboris.Saf.Engine.Report;
    using Collaboris.Saf.Exceptions;
    using Collaboris.Saf.Repositories;
    using Collaboris.Saf.Tracing;
    using Collaboris.Utils;

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Administration;

    #endregion

    /// <summary>
    /// The adapter base.
    /// </summary>
    public abstract class AdapterBase
    {
        #region Constants and Fields

        /// <summary>
        /// The alias path.
        /// </summary>
        private string aliasPath;

        /// <summary>
        /// The current directory.
        /// </summary>
        private string currentDirectory;

        /// <summary>
        /// The farm.
        /// </summary>
        private SPFarm farm;

        /// <summary>
        /// The macro.
        /// </summary>
        private IMacro macro;

        /// <summary>
        /// Stores the instance of the repository responsible for saving and loading MAcro reports
        /// </summary>
        private ISafRepository<MacroReport> reportRepository;

        /// <summary>
        /// The report directory.
        /// </summary>
        // private string reportDirectory;
        /// <summary>
        /// The site.
        /// </summary>
        private SPSite site;

        /// <summary>
        /// The target url.
        /// </summary>
        private string targetUrl;

        /// <summary>
        /// The web.
        /// </summary>
        private SPWeb web;

        private SafContext context;

        /// <summary>
        /// The web application.
        /// </summary>
        private SPWebApplication webApplication;

        private bool forceUndo;

        private string reportDirectory;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AdapterBase"/> class.
        /// </summary>
        /// <param name="reportRepository">
        /// The report repository.
        /// </param>
        protected AdapterBase(ISafRepository<MacroReport> reportRepository)
        {
            ValidationHelper.VerifyObjectArgument(reportRepository, "reportRepository");
            
            this.reportRepository = reportRepository;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AdapterBase"/> class.
        /// </summary>
        protected AdapterBase()
        {
           
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the context.
        /// </summary>
        /// <value>The context.</value>
        public SafContext Context
        {
            get
            {
                return context;
            }
            set
            {
                context = value;
            }
        }

        /// <summary>
        /// Gets or sets the Adapter String for the 
        /// current agent.
        /// </summary>
        /// <value>The current directory.</value>
        public abstract string AdapterName
        {
            get;
        }

        /// <summary>
        /// Gets or sets the path to a Spring.Net
        /// configuration file containing custom
        /// aliases.
        /// </summary>
        /// <remarks>
        /// This property is optional. 
        /// </remarks>
        /// <value>The alias path.</value>
        public string AliasPath
        {
            get
            {
                return this.aliasPath;
            }

            set
            {
                this.aliasPath = value;
            }
        }

        protected virtual ISafRepository<MacroReport> CreateRepository()
        {
            return new FileReportRepository(this.ReportDirectory);
        }


        /// <summary>
        /// Gets or sets the current directory.
        /// </summary>
        /// <remarks>
        /// If the current Directory is not supplied then 
        /// the Environment Current Directory is used.
        /// </remarks>
        /// <value>The current directory.</value>
        public string CurrentDirectory
        {
            get
            {
                if (string.IsNullOrEmpty(this.currentDirectory))
                {
                    TraceHelper.TraceInfo(
                        TraceSwitch, 
                        "AdapterBase", 
                        "No Current (or Working) Directory has been supplied. Default to '{0}'", 
                        Environment.CurrentDirectory);

                    this.currentDirectory = Environment.CurrentDirectory;
                }

                if (!Directory.Exists(currentDirectory))
                {
                    Directory.CreateDirectory(currentDirectory);
                }

                return this.currentDirectory;
            }

            set
            {
                this.currentDirectory = value;
            }
        }

        /// <summary>
        /// Gets or sets the current directory.
        /// </summary>
        /// <remarks>
        /// If the current Directory is not supplied then 
        /// the Environment Current Directory is used.
        /// </remarks>
        /// <value>The current directory.</value>
        public string ReportDirectory
        {
            get
            {
                if (string.IsNullOrEmpty(this.reportDirectory))
                {
                    TraceHelper.TraceInfo(
                        TraceSwitch,
                        "AdapterBase",
                        "No Current (or Working) Directory has been supplied. Default to '{0}'",
                        Environment.CurrentDirectory);

                    this.reportDirectory = Environment.CurrentDirectory;
                }

                if (!Directory.Exists(reportDirectory))
                {
                    Directory.CreateDirectory(reportDirectory);
                }

                return this.reportDirectory;
            }

            set
            {
                this.reportDirectory = value;
            }
        }

        /// <summary>
        /// Gets or sets the current farm.
        /// </summary>
        /// <value>The current farm.</value>
        public SPFarm Farm
        {
            get
            {
                return this.farm;
            }

            set
            {
                this.farm = value;
            }
        }

        /// <summary>
        /// Gets or sets the current mode.
        /// </summary>
        /// <value>The current mode.</value>
        public abstract ProcessMode Mode
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the current site.
        /// </summary>
        /// <value>The current site.</value>
        public SPSite Site
        {
            get
            {
                return this.site;
            }

            set
            {
                this.site = value;
            }
        }

        /// <summary>
        /// Gets or sets the target URL.
        /// </summary>
        /// <value>The target URL.</value>
        public string TargetUrl
        {
            get
            {
                return this.targetUrl;
            }

            set
            {
                this.targetUrl = value;
            }
        }

        /// <summary>
        /// Gets or sets the current web.
        /// </summary>
        /// <value>The current web.</value>
        public SPWeb Web
        {
            get
            {
                return this.web;
            }

            set
            {
                this.web = value;
            }
        }

        /// <summary>
        /// Gets or sets the current web application.
        /// </summary>
        /// <value>The current web application.</value>
        public SPWebApplication WebApplication
        {
            get
            {
                return this.webApplication;
            }

            set
            {
                this.webApplication = value;
            }
        }

        /// <summary>
        /// Gets TraceSwitch.
        /// </summary>
        protected static TraceSwitch TraceSwitch
        {
            get
            {
                return AdaptersTraceHelper.TraceSwitch;
            }
        }

        /// <summary>
        /// Gets Macro.
        /// </summary>
        protected IMacro Macro
        {
            get
            {
                return this.macro;
            }
        }

        /// <summary>
        /// Stores the instance of the repository responsible for saving and loading MAcro reports
        /// </summary>
        protected ISafRepository<MacroReport> ReportRepository
        {
            get
            {
                /** Commented: BalamuruganK
                 * i.e. - with MSBUILD scenario, macro file will be in different folders.
                 * So create a new repository for each macro with that path.
                if (this.reportRepository == null)
                    this.reportRepository = this.CreateRepository();
                 * */
                // New line
                this.reportRepository = this.CreateRepository();

                return this.reportRepository;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Executes the macro.
        /// </summary>
        /// <returns>
        /// </returns>
        public MacroReport ExecuteMacro()
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "AdapterBase", "ExecuteMacro");

            MacroReport report = null;
            string macroName = null;

            // Commented : BalamuruganK - macroName will be empty.
            // string reportName = BuildReportName(macroName, this.Mode);
            string reportName = string.Empty;

            try
            {
                // create our local SP objects and then the context.
                this.InitaliseSPObjects();

                context = this.GetContext();

                // Commented: BalamuruganK - Initialise the context.
                context.Farm = this.farm;
                context.Web = this.web;
                context.Site = this.site;

                // now lets go add the shortcuts
                this.AddPlaceHolders(context);

                this.macro = this.GetMacro(context);

                // Call into the Adapters to get the File Name.
                macroName = this.GetMacroName();

                // Get the report name from the macro name.
                reportName = BuildReportName(macroName, this.Mode);

                TraceHelper.TraceInfo(
                    TraceSwitch, "AdapterBase", "Started  - Macro ({0}) ({1})", this.macro.TemplateID, this.Mode);

                if (this.Mode == ProcessMode.Do)
                {
                    // run the macro which will create the new web.
                    report = this.macro.Do(context);
                }
                else
                {
                    // Gets the old Report that was created by the Do (if there is one).
                    report = this.PerformUndoOnAdapter(macroName, report);
                }

                if (report != null)
                {
                    this.SaveReport(report, reportName);
                }
            }
            catch (ActionRunException ex)
            {
                if (!String.IsNullOrEmpty(macroName))
                {
                    this.SaveReport(ex.ProgressReport as MacroReport, reportName);
                }

                TraceHelper.TraceException(TraceSwitch, "AdapterBase", "Run", ex);

                // have to do this here as this Fature can be run by many processes 
                // not just IIS, so we cant rely on anything further up stack
                // logging it for us. 
                TraceHelper.TraceError(
                    TraceSwitch, "AdapterBase", "Run(): Unable to run this event {0} on the feature.", this.Mode);

                throw;
            }
            catch (Exception ex)
            {
                TraceHelper.TraceException(TraceSwitch, "AdapterBase", "Run", ex);

                // have to do this here as this Fature can be run by many processes 
                // not just IIS, so we cant rely on anything further up stack
                // logging it for us. 
                TraceHelper.TraceError(
                    TraceSwitch, "AdapterBase", "Run(): Unable to run this event {0} on the feature.", this.Mode);

                throw;
            }
            finally
            {
                this.DisposeSPObjects();

                TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "ExecuteMacro");
                TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "ExecuteMacro");
            }

            return report;
        }

        /// <summary>
        /// Performs the undo on adapter.
        /// </summary>
        /// <remarks>
        /// This will attempt to get the MacroReport generated on the "Do".
        /// If it cant find it - the macro will be run if ForceUndo is set to true.
        /// </remarks>
        /// <param name="macroName">Name of the macro.</param>
        /// <param name="report">The report.</param>
        /// <returns></returns>
        private MacroReport PerformUndoOnAdapter(string macroName, MacroReport report)
        {
            MacroReport oldReport = this.GetReport(this.context, macroName, ProcessMode.Do);
            if (oldReport != null && this.context != null)
            {
                this.AddUserPropertiesGeneratedFromDo(this.context, oldReport);
                this.context.LastDoReport = oldReport;
            }

            
            if (oldReport != null)
            {
                report = this.macro.Undo(this.context);
            }
            else
            {
                if (this.ForceUndo)
                {
                    // we have no macro report available, so lets go run anyway.
                    this.Macro.Undo(this.context);
                }
                else
                {

                    string errMessage = "No previous 'UnDo' report can be located for this Macro. " + Environment.NewLine +
                                        "This usually occurs when the Report has been deleted, or if the Macros 'Do' has never been run. " + Environment.NewLine +
                                        "If you still wish to call 'Undo' please 'ForceUndo' on the relevant adapter";
                    throw new MacroReportMissingException(errMessage);
                }
            }

            return report;
        }

        /// <summary>
        /// Gets or sets a value indicating whether [force undo].
        /// </summary>
        /// <remarks>
        /// ForceUndo will be used to determine whether to run a macro 
        /// even if there is no Macro Report from the Do.
        /// </remarks>
        /// 
        /// <value><c>true</c> if [force undo]; otherwise, <c>false</c>.</value>
        public bool ForceUndo
        {
            get
            {
                return this.forceUndo;
            }
            set
            {
                this.forceUndo = value;
            }
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// The build report name.
        /// </summary>
        /// <param name="macroName">
        /// The macro name.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <returns>
        /// The build report name.
        /// </returns>
        protected static string BuildReportName(string macroName, ProcessMode mode)
        {
            return macroName + "_" + mode;
        }

        /// <summary>
        /// Called when the Adapter needs to add PlaceHolders 
        /// from (its specific environment) to a SAF Macro.
        /// </summary>
        /// <param name="ctx">
        /// </param>
        protected abstract void AddProperties(SafContext ctx);

        /// <summary>
        /// Gets a new Action Context based on either a
        /// current Web or current Farm.
        /// </summary>
        /// <returns>
        /// returns an SafContext
        /// </returns>
        protected virtual SafContext GetContext()
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "AdapterBase", "GetContext");

            SafContext ctx;

            if (this.web != null)
            {
                TraceHelper.TraceInfo(TraceSwitch, "AdapterBase", "Context  - Web mode as a valid 'url' was passed!");
                ctx = SafContext.CreateContext(this.web, SPScope.Web);
            }
            else
            {
                if (this.Farm != null)
                {
                    TraceHelper.TraceInfo(TraceSwitch, "AdapterBase", "Context  - Farm mode as no 'url' was passed!");
                    ctx = SafContext.CreateContext(this.farm, SPScope.Farm);
                }
                else
                {
                    TraceHelper.TraceInfo(
                        TraceSwitch, "AdapterBase", "Context  - Non SharePoint mode as no 'url' or 'Farm' exists!");
                    ctx = SafContext.CreateContext();
                }
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "GetContext");
            return ctx;
        }

        /// <summary>
        /// Called when the Adapter Requires a Macro to be
        /// instatiated and returned.
        /// </summary>
        /// <param name="ctx">
        /// The SafContext.
        /// </param>
        /// <returns>
        /// An Instantiated Macro, created using Spring.Net
        /// </returns>
        protected abstract IMacro GetMacro(SafContext ctx);

        /// <summary>
        /// Gets the name of the macro.
        /// </summary>
        /// <returns>
        /// The name of the macro
        /// </returns>
        protected abstract string GetMacroName();

        /// <summary>
        /// Gets the correct Macro Report. Default implementation will load it from disk.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="reportName">
        /// The report Name.
        /// </param>
        /// <returns>
        /// </returns>
        protected virtual MacroReport GetReport(SafContext context, string reportName)
        {
            ValidationHelper.VerifyObjectArgument(context, "SafContext");
            ValidationHelper.VerifyStringArgument(reportName, "reportName");

            try
            {
                // var repository = new FileReportRepository(this.CurrentDirectory);
                return this.ReportRepository.LoadObject(BuildReportName(reportName, this.Mode));
            }
            catch (FileNotFoundException ex)
            {
                return null;
            }
        }

        /// <summary>
        /// Gets the report.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="macroName">
        /// Name of the macro.
        /// </param>
        /// <param name="mode">
        /// The mode.
        /// </param>
        /// <returns>
        /// </returns>
        protected virtual MacroReport GetReport(SafContext context, string macroName, ProcessMode mode)
        {
            ValidationHelper.VerifyObjectArgument(context, "SafContext");
            ValidationHelper.VerifyStringArgument(macroName, "macroName");

            try
            {
                // var repository = new FileSystemReportRepository(this.CurrentDirectory);
                // return repository.LoadObject(BuildReportName(macroName, mode));
                return this.ReportRepository.LoadObject(BuildReportName(macroName, mode));
            }
            catch (FileNotFoundException ex)
            {
                return null;
            }
        }

        /// <summary>
        /// Saves the <see cref="MacroReport"/> to disk.
        /// </summary>
        /// <param name="report">
        /// The report.
        /// </param>
        /// <param name="reportName">
        /// The report Name.
        /// </param>
        protected virtual void SaveReport(MacroReport report, string reportName)
        {
            ValidationHelper.VerifyObjectArgument(report, "report");

            ReportRepository.SaveObject(reportName, report);
        }

        /// <summary>
        /// Adds the PlaceHolders that will be substituted at run time.
        /// </summary>
        /// <param name="ctx">
        /// The ActionConext
        /// </param>
        private void AddPlaceHolders(SafContext ctx)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "AdapterBase", "AddPlaceHolders");
            this.AddProperties(ctx);

            ctx.ContextProperties.Add("WorkingDirectory", this.CurrentDirectory, true);

            // this added for backward compatability
            ctx.ContextProperties.Add("CurrentDirectory", this.CurrentDirectory, true);
            ctx.ContextProperties.Add("AdapterName", this.AdapterName, true);

            TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "AddPlaceHolders");
        }

        /// <summary>
        /// Adds the user properties generated from do.
        /// </summary>
        /// <param name="ctx">
        /// The Context to Add the Place Holders to.
        /// </param>
        /// <param name="oldReport">
        /// The old report.
        /// </param>
        private void AddUserPropertiesGeneratedFromDo(ContextBase ctx, MacroReport oldReport)
        {
            ValidationHelper.VerifyObjectArgument(oldReport, "oldReport");
            ValidationHelper.VerifyObjectArgument(ctx, "ctx");

            foreach (string key in oldReport.UserProperties.Keys)
            {
                ctx.ActionProperties.Add(key, oldReport.UserProperties[key], true);
            }
        }

        /// <summary>
        /// Binds the local farm to a mod variable
        /// </summary>
        private void BindFarm()
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "AdapterBase", "BindFarm");

            try
            {
                this.farm = SPFarm.Local;
            }
            catch (Exception ex)
            {
                TraceHelper.TraceException(TraceSwitch, "AdapterBase", "BindFarm()", ex);
                throw new ApplicationException(
                    "Unable to bind to an SPFarm.Local -" + " Please check SharePoint is installed on this machine.", ex);
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "BindFarm");
            }
        }

        /// <summary>
        /// Binds the new SPSite to a mod variable
        /// </summary>
        private void BindSite()
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "AdapterBase", "BindSite");

            // Create SPSite
            try
            {
                this.site = new SPSite(this.TargetUrl);
            }
            catch (Exception ex)
            {
                TraceHelper.TraceException(TraceSwitch, "AdapterBase", "BindSite()", ex);
                throw new ApplicationException("Unable to bind to an SPSite at this Url : '" + this.TargetUrl + "'", ex);
            }
            finally
            {
                if (this.site == null)
                {
                    throw new ApplicationException("Unable to bind to an SPSite at this Url : '" + this.TargetUrl + "'");
                }

                TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "BindSite");
            }
        }

        /// <summary>
        /// Binds the new SPWeb to a mod variable
        /// </summary>
        private void BindWeb()
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "AdapterBase", "BindWeb");

            // Create SPWeb
            try
            {
                this.web = this.site.OpenWeb();
                if (!this.web.Exists)
                {
                    throw new ApplicationException(
                        String.Format(
                            "Attempted to call OpenWeb at this url '{0}'. Unable to do so as the Web doesn't exist!", 
                            this.TargetUrl));
                }
            }
            catch (Exception ex)
            {
                TraceHelper.TraceException(TraceSwitch, "AdapterBase", "BindWeb()", ex);
                throw new ApplicationException("Unable to open an SPWeb at this Url : '" + this.TargetUrl + "'", ex);
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "BindWeb");
            }

            if (this.web == null)
            {
                throw new ApplicationException("Unable to open an SPWeb at this Url : '" + this.TargetUrl + "'");
            }
        }

        /// <summary>
        /// Disposes the current SPSite and SPWeb
        /// </summary>
        private void DisposeSPObjects()
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "AdapterBase", "DisposeSPObjects");

            if (this.web != null)
            {
                this.web.Dispose();
                this.web = null;
            }

            if (this.site != null)
            {
                this.site.Dispose();
                this.site = null;
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "DisposeSPObjects");
        }

        /// <summary>
        /// Gets a reference to a Farm, Site, SPWebApp
        /// requires a valid TargetUrl property to be set.
        /// </summary>
        private void InitaliseSPObjects()
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "AdapterBase", "InitaliseSPObjects");
            try
            {
                if (string.IsNullOrEmpty(this.TargetUrl))
                {
                    this.BindFarm();
                }
                else
                {
                    this.BindSite();
                    this.BindWeb();

                    // also set the Web App and Farm up for convenience
                    this.webApplication = this.site.WebApplication;
                    this.farm = this.webApplication.Farm;
                }
            }
            catch (Exception ex)
            {
                TraceHelper.TraceException(TraceSwitch, "AdapterBase", "InitaliseSPObjects", ex);
                throw;
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "AdapterBase", "InitaliseSPObjects");
            }
        }

        #endregion
    }
}